﻿#ifndef FFMPEG_PLAYER_HTTP_SERVER_H
#define FFMPEG_PLAYER_HTTP_SERVER_H


#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/strand.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <sstream>

#include "log.h"
#include "base_net.hpp"

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = boost::beast::http;    // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>


// Report a failure
void fail(beast::error_code ec, char const *what) {
    LOG_ERROR << what << ": " << ec.message();
}

class HttpServer : public BaseNet, std::enable_shared_from_this<HttpServer> {

    websocket::stream<beast::tcp_stream> ws_;
    beast::flat_buffer buffer_;

public:
    // Take ownership of the socket
    explicit HttpServer(tcp::socket &&socket)
            : BaseNet(), ws_(std::move(socket)) {
    }

    // Start the asynchronous operation
    void run() {
        // Set suggested timeout settings for the websocket
        ws_.set_option(
                websocket::stream_base::timeout::suggested(
                        beast::role_type::server));

        // Set a decorator to change the Server of the handshake
        ws_.set_option(websocket::stream_base::decorator(
                [](websocket::response_type &res)
                {
                    res.set(http::field::server,
                            std::string("web Iot server") +
                            " 1.0");
                }));


        beast::flat_buffer buffer;

        // Read the HTTP request ourselves
        http::request<http::string_body> req;
        http::read(ws_.next_layer(), buffer, req);

        // See if its a WebSocket upgrade request
        if (websocket::is_upgrade(req))
        {
            // Construct the stream, transferring ownership of the socket
            //stream<tcp_stream> ws(std::move(sock));

            // Clients SHOULD NOT begin sending WebSocket
            // frames until the server has provided a response.
            BOOST_ASSERT(buffer.size() == 0);

            // Accept the upgrade request


            ws_.async_accept(req,
                             beast::bind_front_handler(
                                     &HttpServer::on_accept,
                                     shared_from_this()));

            printf("path: %s\n", std::string(req.target().data(), req.target().length()).data());
        }
    }

    void on_accept(beast::error_code ec) {
        if (ec)
        {
            LOG_ERROR << "accept" << ": " << ec.message();
        }

        // Read a message

        do_read();
    }


    void do_read() {

        // Read a message into our buffer
        ws_.async_read(
                buffer_,
                beast::bind_front_handler(
                        &HttpServer::on_read,
                        shared_from_this()));
    }

    void on_read(
            beast::error_code ec,
            std::size_t bytes_transferred) {
        boost::ignore_unused(bytes_transferred);

        // This indicates that the session was closed
        if (ec == websocket::error::closed){
            LOG_INFO << "return ";
            return;
        }


        if (ec)
            LOG_ERROR << "read" << ": " << ec.message();

        // Echo the message
        std::stringstream ss;
        ss << boost::beast::make_printable(buffer_.data());
        std::cout << ss.str();
        ws_.text(ws_.got_text());
        ws_.async_write(
                buffer_.data(),
                beast::bind_front_handler(
                        &HttpServer::on_write,
                        shared_from_this()));
    }

    void on_write(
            beast::error_code ec,
            std::size_t bytes_transferred) {
        boost::ignore_unused(bytes_transferred);

        if (ec)
            LOG_ERROR << "write" << ": " << ec.message();

        // Clear the buffer
        buffer_.consume(buffer_.size());

        // Do another read
        do_read();
    }
};

//------------------------------------------------------------------------------

// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener> {
    net::io_context &ioc_;
    tcp::acceptor acceptor_;

public:
    listener(
            net::io_context &ioc,
            const tcp::endpoint& endpoint)
            : ioc_(ioc), acceptor_(ioc) {
        beast::error_code ec;

        // Open the acceptor
        acceptor_.open(endpoint.protocol(), ec);
        if (ec)
        {
            LOG_ERROR << "open" << ": " << ec.message();
            return;
        }

        // Allow address reuse
        acceptor_.set_option(net::socket_base::reuse_address(true), ec);
        if (ec)
        {
            LOG_ERROR << "set_option" << ": " << ec.message();
            return;
        }

        // Bind to the server address
        acceptor_.bind(endpoint, ec);
        if (ec)
        {
            LOG_ERROR << "bind" << ": " << ec.message();
            fail(ec, "bind");
            return;
        }

        // Start listening for connections
        acceptor_.listen(
                net::socket_base::max_listen_connections, ec);
        if (ec)
        {
            LOG_ERROR << "listen" << ": " << ec.message();
            return;
        }
    }

    // Start accepting incoming connections
    void run() {
        do_accept();
    }

private:
    void do_accept() {
        // The new connection gets its own strand
        acceptor_.async_accept(
                net::make_strand(ioc_),
                beast::bind_front_handler(
                        &listener::on_accept,
                        shared_from_this()));
    }

    void on_accept(beast::error_code ec, tcp::socket socket) {
        if (ec)
        {
            LOG_ERROR << "accept" << ": " << ec.message();
        }
        else
        {
            // Create the session and run it
            std::make_shared<HttpServer>(std::move(socket))->run();
        }

        // Accept another connection
        do_accept();
    }
};

#endif